package com.shareyi.baseproject.kernel.adapter.spi.sequence;

/**
 * 本地雪花id生成器
 * (只使用时间戳和时间戳的序列号)
 * 分布式情况下，单机会产生重复，可以利用redis来进行防重
 *
 * @author yuli
 * @date 2020/7/27
 */
public class ShortSnowIDGenerator {

    /**
     * 2018-1-1  减少时间跨度;
     */
    private static final long EPOCH = 1514736000000L;
    /**
     * seq 占用的bit数量；
     */
    private static final long SEQ_BITS = 7L;   // 7bit, 128
    /**
     * 时间戳暂用的bit数量；
     */
    private static final long TIMESTAMP_BITS = 41L;
    /**
     * 时间戳需要位移的bit, 空出序列号的位置；
     */
    private static final long TIMESTAMP_SHIFT = SEQ_BITS;
    /**
     * 序列号需要掩码
     */
    private static final long SEQ_MASK = ~(-1L << SEQ_BITS);
    /**
     * 时间戳的掩码
     */
    private static final long TIMESTAMP_MASK = ~(-1L << TIMESTAMP_BITS);

    /**
     * 当前时间戳下的序列号
     */
    private static long seq = 0L;
    /**
     * 上一个时间戳
     */
    private static long lastTimestamp = 0L;

    /**
     * 生成序列号
     *
     * @return
     */
    public synchronized static long next() {
        long timestamp = getTimestamp();

        if (lastTimestamp == timestamp) {
            seq = maskValue(seq + 1, SEQ_MASK, 0L);
            if (seq == 0L) {
                timestamp = tillNext(lastTimestamp);
            }
        } else {
            seq = 0L;
        }
        lastTimestamp = timestamp;
        long timePart = maskValue(timestamp, TIMESTAMP_MASK, TIMESTAMP_SHIFT);
        return timePart | seq;
    }

    /**
     * 等待下一秒
     *
     * @param lastTimestamp
     * @return
     */
    private static long tillNext(long lastTimestamp) {
        long timestamp = getTimestamp();
        while (timestamp <= lastTimestamp) {
            timestamp = getTimestamp();
        }
        return timestamp;
    }

    /**
     * 获取时间戳，并减去epoch时间
     *
     * @return
     */
    private static long getTimestamp() {
        return System.currentTimeMillis() - EPOCH;
    }

    /**
     * mask value
     *
     * @param value
     * @param mask
     * @param shift
     * @return
     */
    private static long maskValue(long value, long mask, long shift) {
        return (value & mask) << shift;
    }

}
